use std::{borrow::Borrow, rc::Rc};

use crate::{parse, runtime, Env, Expr, Parser};

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("Runtime: {0}")]
    Runtime(#[from] runtime::Error),
    #[error("Expression mismatch. Expect '{expect}'.")]
    ExprMismatch { expect: &'static str },
    #[error("Unrecognized identifier '{0}'.")]
    UnrecognizedIdent(String),
    #[error("Pop from empty exprs")]
    PopFromEmptyExprs,
    #[error("Too few exprs on '{op}'. Expect {expect}")]
    TooFewExprs { op: &'static str, expect: usize },
    #[error("No such key {0}")]
    NoSuchKey(String),
    #[error("Mapping ({0}) failed")]
    MappingFailure(&'static str),
}

pub trait ParseFromExpr: Sized {
    fn from_expr(expr: Expr) -> crate::Result<Self>;
}

pub trait EvalFromExpr: Sized {
    fn from_expr(expr: Expr, env: Rc<Env>) -> crate::Result<Self>;
}

pub fn parse_to<S: Borrow<str>, T: ParseFromExpr>(src: S) -> crate::Result<T> {
    T::from_expr(
        Parser::new(src.borrow()).parse_single_expr()?,
    )
}

pub fn eval_to<T: EvalFromExpr>(src: &str) -> crate::Result<T> {
    eval_to_with(src, Env::root())
}

pub fn eval_to_with<T: EvalFromExpr>(src: &str, env: Rc<Env>) -> crate::Result<T> {
    T::from_expr(parse(src)?, env)
}

impl ParseFromExpr for bool {
    fn from_expr(expr: Expr) -> crate::Result<Self> {
        Ok(expr.into_bool_literal().ok_or(Error::ExprMismatch {
            expect: "bool literal",
        })?)
    }
}

impl ParseFromExpr for i32 {
    fn from_expr(expr: Expr) -> crate::Result<Self> {
        Ok(expr.into_i32_literal().ok_or(Error::ExprMismatch {
            expect: "int literal",
        })?)
    }
}

impl ParseFromExpr for f32 {
    fn from_expr(expr: Expr) -> crate::Result<Self> {
        Ok(expr.into_f32_literal().ok_or(Error::ExprMismatch {
            expect: "real literal",
        })?)
    }
}

impl ParseFromExpr for String {
    fn from_expr(expr: Expr) -> crate::Result<Self> {
        Ok(expr.into_string_literal().ok_or(Error::ExprMismatch {
            expect: "string literal",
        })?)
    }
}

impl Expr {
    pub fn parse_into<T: ParseFromExpr>(self) -> crate::Result<T> {
        T::from_expr(self)
    }

    pub fn eval_into<T: EvalFromExpr>(self, env: Rc<Env>) -> crate::Result<T> {
        T::from_expr(self, env)
    }
}

pub trait ExprVecExt {
    fn pop_into<T: ParseFromExpr>(&mut self) -> crate::Result<T>;
    fn pop_map<U, F: FnOnce(Expr) -> Option<U>>(&mut self, f: F) -> runtime::Result<U>;
    fn pop_or_err(&mut self, err: runtime::Error) -> runtime::Result<Expr>;
    fn try_pop(&mut self) -> runtime::Result<Expr> {
        self.pop_or_err(runtime::Error::PopFromEmptyExprs)
    }
    fn pop_from_kv(&mut self, key: &str) -> Result<Expr, Error>;

    fn pop_from_kv_and_map<U, F: FnOnce(Expr) -> Option<U>>(&mut self, key: &str, f: F) -> Result<U, Error> {
        f(self.pop_from_kv(key)?).ok_or(Error::MappingFailure(std::any::type_name::<F>()))
    }
}

impl ExprVecExt for Vec<Expr> {
    fn pop_into<T: ParseFromExpr>(&mut self) -> crate::Result<T> {
        self.pop().ok_or(Error::PopFromEmptyExprs.into()).and_then(T::from_expr)
    }

    fn pop_map<U, F: FnOnce(Expr) -> Option<U>>(&mut self, f: F) -> runtime::Result<U> {
        self.pop().and_then(f).ok_or(runtime::Error::PopFromEmptyExprs)
    }

    fn pop_or_err(&mut self, err: runtime::Error) -> runtime::Result<Expr> {
        self.pop().ok_or(err)
    }

    fn pop_from_kv(&mut self, key: &str) -> Result<Expr, Error> {
        for i in 0..self.len() {
            if self[i].is_kv_pair(key) {
                return Ok(self.remove(i).into_kv_pair().unwrap().1)
            }
        }
        Err(Error::NoSuchKey(key.into()))
    }
}